package uk.ac.lkl.lpp.model;



import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.ac.lkl.common.associator.Associator;

import uk.ac.lkl.common.util.ds.AbstractNotifyingList;
import uk.ac.lkl.common.util.ds.NotifyingList;

import uk.ac.lkl.lpp.model.PedagogyPlanModel;

import uk.ac.lkl.lpp.model.ldo.CognitiveActivity;
import uk.ac.lkl.lpp.model.ldo.TeachingMethod;


public class TeachingMethodCognitiveActivitiesFieldGroup extends
        TeachingMethodFieldGroup {

    private AbstractNotifyingList<CognitiveActivity> cognitiveActivities;


    private Associator<TeachingMethod, Float, CognitiveActivity> associator;


    private Map<TeachingMethod, Float> hoursMap;


    public TeachingMethodCognitiveActivitiesFieldGroup(PedagogyPlanModel model) {
        super(model);
        this.cognitiveActivities = model.getList(CognitiveActivity.class);
        this.associator = model.getAssociator(
                                              "main",
                                              TeachingMethod.class,
                                              Float.class,
                                              null,
                                              CognitiveActivity.class);
        this.hoursMap = model.getMap(
                                     "TeachingMethodHoursAllocation",
                                     TeachingMethod.class,
                                     Float.class);
    }


    /**
     * This is where the logic of setting hours/cognitive activity hours works.
     * Needs cleaning up.
     * 
     */
    protected void setField(TeachingMethod teachingMethod,
                            int fieldIndex,
                            Object object) {
        Float totalHours = hoursMap.get(teachingMethod);
        if (totalHours == null)
            return;

        CognitiveActivity updatedCognitiveActivity = cognitiveActivities
                .get(fieldIndex);
        ArrayList<CognitiveActivity> associatedCognitiveActivities = associator
                .getRightAssociations(teachingMethod);

        // map for each cognitive activity
        HashMap<CognitiveActivity, Float> targetHoursMap = new HashMap<CognitiveActivity, Float>();

        float restHours = 0.0f;
        for (CognitiveActivity associatedCognitiveActivity : associatedCognitiveActivities) {
            if (associatedCognitiveActivity != updatedCognitiveActivity) {
                Float weight = associator
                        .getAnnotation(
                                       teachingMethod,
                                       associatedCognitiveActivity);
                if (weight == null)
                    continue;

                float hours = weight * totalHours;

                targetHoursMap.put(associatedCognitiveActivity, hours);

                restHours += hours;
            }
        }

        float newTotalHours = restHours;
        if (object != null) {
            newTotalHours += (Float) object;
            if ((Float) object == 0.0f)
                object = null;
        }

        // object may be null but this is fine since will be set to
        // null below
        targetHoursMap.put(updatedCognitiveActivity, (Float) object);

        for (Map.Entry<CognitiveActivity, Float> entry : targetHoursMap
                .entrySet()) {
            CognitiveActivity associatedCognitiveActivity = entry.getKey();
            Float targetHours = entry.getValue();
            Float newWeight = targetHours == null ? null : targetHours
                    / newTotalHours;
            associator.setAnnotation(
                                     teachingMethod,
                                     associatedCognitiveActivity,
                                     newWeight);
        }

        hoursMap.put(teachingMethod, newTotalHours);
    }


    protected Object getField(int listIndex,
                              TeachingMethod teachingMethod,
                              int fieldIndex) {
        CognitiveActivity cognitiveActivity = cognitiveActivities
                .get(fieldIndex);
        Float weight = associator.getAnnotation(
                                                teachingMethod,
                                                cognitiveActivity);

        if (weight == null)
            return null;

        Float hours = hoursMap.get(teachingMethod);
        if (hours == null)
            return null;

        return weight * hours;
    }


    @Override
    public String getFieldName(int fieldIndex) {
        return cognitiveActivities.get(fieldIndex).getTitle();
    }


    public Object getHeader(int fieldIndex) {
        return cognitiveActivities.get(fieldIndex);
    }


    public Class getHeaderClass(int fieldIndex) {
        return CognitiveActivity.class;
    }


    public Class getClass(int fieldIndex) {
        return Float.class;
    }


    @Override
    public String getName() {
        return "Cognitive Activities";
    }


    @Override
    public boolean isPermanent() {
        return false;
    }


    public boolean isEditable(TeachingMethod teachingMethod, int fieldIndex) {
        Float hours = hoursMap.get(teachingMethod);
        return hours != null;
    }


    @Override
    public int size() {
        return cognitiveActivities.size();
    }


    @Override
    public Object calculateTotal(int fieldIndex) {
        CognitiveActivity cognitiveActivity = cognitiveActivities
                .get(fieldIndex);

        List<TeachingMethod> teachingMethods = associator.getLeftList();

        Float total = 0.0f;

        for (TeachingMethod teachingMethod : teachingMethods) {
            Float weight = associator.getAnnotation(
                                                    teachingMethod,
                                                    cognitiveActivity);
            if (weight == null)
                continue;

            Float hours = hoursMap.get(teachingMethod);

            if (hours == null)
                continue;

            total += weight * hours;
        }

        return total;
    }

}
